using Gtk;
using System;
using System.Collections;

namespace TVListings
{
	public class ProgramDisplay : Gtk.Bin
	{
		public class ColorMapping { public string Category; public Gdk.Color Color; }

		protected ArrayList ChannelWidgets;
		protected ArrayList TimeWidgets;
		protected ArrayList ProgramWidgets;
		protected DateTime BaseTime;
		
		protected int mColumnWidthRequest = 150;
		protected int mTimeSpan = 30;
		protected Hashtable mColorings;
		protected TVGuide mGuide;

		protected Gtk.Tooltips Tips;
		protected Gtk.Table DisplayTable;
		protected Gtk.Button BackButton;
		protected Gtk.Button ForwardButton;

		public ProgramDisplay()
		{
			Stetic.Gui.Build(this, typeof(ProgramDisplay));

			Tips = new Tooltips();
			ChannelWidgets = new ArrayList();
			TimeWidgets = new ArrayList();
			ProgramWidgets = new ArrayList();
			mColorings = new Hashtable();

			LoadGuide();
			ShowTime(DateTime.Now);
		}
		
		private void LoadGuide()
		{
			ClearDisplay();
			if (mGuide == null)
				return;

			uint Channels = (uint)mGuide.Channels.Length;
			DisplayTable.Resize(Channels + 1, DisplayTable.NColumns);
			((Table.TableChild)DisplayTable[ForwardButton]).BottomAttach = Channels + 1;
			((Table.TableChild)DisplayTable[BackButton]).BottomAttach = Channels + 1;
			
			ShowChannels();
		}
		
		private void ShowTime(DateTime Time)
		{
			Time = new DateTime(Time.Year, Time.Month, Time.Day,
				Time.Hour, (Time.Minute / mTimeSpan) * mTimeSpan, 0);

			BaseTime = Time;
			ClearPrograms();
			ClearTimes();
			DateTime[] Times = ShowTimes(Time);
			
			if (mGuide == null)
				goto Finish;

			ShowPrograms(Times);
			
			BackButton.Sensitive = Time > mGuide.StartTime;
			ForwardButton.Sensitive = Times[Times.Length - 1].AddMinutes(mTimeSpan) < mGuide.StopTime;
		Finish:
			BackButton.Visible = ForwardButton.Visible = 
				BackButton.Sensitive || ForwardButton.Sensitive;
			DisplayTable.ShowAll();
		}
		
		private void ShowChannels()
		{
			if (mGuide == null)
				return;

			for (uint i = 0; i < mGuide.Channels.Length; ++i)
			{
				Channel c = mGuide.Channels[i];
				Widget w = MakeWidget(c.ChannelNumber.ToString(),
					c.DisplayNames[c.DisplayNames.Length - 1].Value, "Channel");
				Colorize(w, "Channel");
				DisplayTable.Attach(w, 0, 1, i + 1, i + 2);
				/// TODO: Make channel column smaller
			}		
			DisplayTable.ShowAll();
		}

		private DateTime[] ShowTimes(DateTime Time)
		{
			DateTime[] Times = new DateTime[DisplayTable.NColumns - 3];
			for (uint i = 0; i < Times.Length; ++i)
			{
				Times[i] = Time.AddMinutes(i * mTimeSpan);
				Widget w = MakeWidget(Times[i].ToString("h:mm tt"), Times[i].ToString("dddd, MMM. d"), "Time");
				Colorize(w, "Time");
				DisplayTable.Attach(w, i + 2, i + 3, 0, 1);
			}
			DisplayTable.ShowAll();
			return Times;
		}
		
		private void ShowPrograms(DateTime[] Times)
		{
			if (Times.Length == 0)
				return;

			for (uint c = 0; c < mGuide.Channels.Length; ++c)
			{
				Channel chan = mGuide.Channels[c];
				Program[] progs = mGuide.GetProgramsByChannel(chan.ID);
				int prog = 0;
				for ( ; prog < progs.Length && 
						progs[prog].Stop < Times[0].AddMinutes(mTimeSpan / 2); ++prog)
					continue;
				
				for ( ; prog < progs.Length && 
						progs[prog].Start < Times[Times.Length - 1].AddMinutes(mTimeSpan / 2); ++prog)
				{
					Program p = progs[prog];

					uint Start, Stop;
					for (Start = 0; Start < Times.Length &&
							Times[Start] < p.Start.AddMinutes(-mTimeSpan / 2); ++Start)
						continue;
					for (Stop = Start + 1; Stop < Times.Length &&
							Times[Stop] < p.Stop.AddMinutes(-mTimeSpan / 2); ++Stop)
						continue;
					if (Start >= Times.Length || Stop <= Start)
						continue;

					string Desc = p.ToString();
					if (p.Start != Times[Start] || (Stop < Times.Length && p.Stop != Times[Stop]))
						Desc += " (" + p.Start.ToString("h:mm") + " - " + p.Stop.ToString("h:mm") + ")";
					if (p.Description != null)
						Desc += "\n" + p.Description[0].Value;

					Widget w = MakeWidget(p.ToString(), Desc, "Program");

					if (p.Category != null)
						foreach (Descriptor d in p.Category)
							Colorize(w, d.Value);
					if (mGuide.IsMovie(p))
						Colorize(w, "Movie");

					DisplayTable.Attach(w, Start + 2, Stop + 2, c + 1, c + 2);
				}
			}
			DisplayTable.ShowAll();
		}
		
		private Widget MakeWidget(string Text, string Tooltip, string TipCategory)
		{
			Label l = new Label(Text);
			l.Ellipsize = Pango.EllipsizeMode.End;
			l.Justify = Justification.Center;
			l.LineWrap = true;
			l.Wrap = true;
			Frame f = new Frame();
			f.Add(l);
			EventBox e = new EventBox();
			e.Add(f);
			
			if (Tooltip != null)
			{
				Tips.SetTip(e, Tooltip, TipCategory);
			}
			return e;
		}
		
		private void Colorize(Widget w, string Category)
		{
			if (mColorings.Contains(Category))
				w.ModifyBg(StateType.Normal, (Gdk.Color)mColorings[Category]);
		}
		
		private void ClearPrograms()
		{
			foreach (Widget w in ProgramWidgets)
			{
				DisplayTable.Remove(w);
				ProgramWidgets.Remove(w);
			}
		}
		
		private void ClearTimes()
		{
			foreach (Widget w in TimeWidgets)
			{
				DisplayTable.Remove(w);
				TimeWidgets.Remove(w);
			}
		}

		private void ClearChannels()
		{
			foreach (Widget w in ChannelWidgets)
			{
				DisplayTable.Remove(w);
				ChannelWidgets.Remove(w);
			}
		}
		
		private void ClearDisplay()
		{
			ClearPrograms();
			ClearChannels();
			ClearTimes();
		}
		
		private void ResizeDisplay(int Width)
		{
			int Avail = Width - BackButton.WidthRequest - ForwardButton.WidthRequest;
			uint Columns = (uint)(Avail / mColumnWidthRequest);
			
			if (Columns + 3 != DisplayTable.NColumns)
			{
				ClearPrograms();
				ClearTimes();
				DisplayTable.Resize(DisplayTable.NRows, Columns + 3);
				Table.TableChild c = (Table.TableChild)DisplayTable[ForwardButton];
				c.LeftAttach = DisplayTable.NColumns - 1;
				c.RightAttach = DisplayTable.NColumns;
				ShowTime(BaseTime);
			}
		}

		protected virtual void OnBackButtonClicked(object sender, System.EventArgs e)
		{
			ShowTime(BaseTime.AddMinutes(-mTimeSpan));
		}

		protected virtual void OnForwardButtonClicked(object sender, System.EventArgs e)
		{
			ShowTime(BaseTime.AddMinutes(mTimeSpan));
		}

		protected virtual void OnDisplayTableSizeAllocated(object o, Gtk.SizeAllocatedArgs args)
		{
			ResizeDisplay(args.Allocation.Width);
		}

		protected virtual void OnNowButtonClicked(object sender, System.EventArgs e)
		{
			ShowTime(DateTime.Now);
		}
		
		public int TimeSpan
		{
			get { return mTimeSpan; }
			set { mTimeSpan = value; ShowTime(BaseTime); }
		}
		
		public int ColumnWidthRequest
		{
			get { return mColumnWidthRequest; }
			set { mColumnWidthRequest = value; ResizeDisplay(DisplayTable.Allocation.Width); }
		}
		
		public TVGuide Guide
		{
			get { return mGuide; }
			set { mGuide = value; LoadGuide(); ShowTime(DateTime.Now); }
		}
		
		public ColorMapping[] Colorings
		{
			get
			{
				ArrayList cols = new ArrayList();
				foreach (DictionaryEntry d in mColorings)
				{
					ColorMapping c = new ColorMapping();
					c.Category = (string)d.Key;
					c.Color = (Gdk.Color)d.Value;
				}
				return (ColorMapping[])cols.ToArray(typeof(ColorMapping));
			}
			set
			{
				mColorings.Clear();
				foreach (ColorMapping c in value)
					mColorings[c.Category] = c.Color;
				ClearDisplay();
				ShowChannels();
				ShowTime(BaseTime);
			}
		}
	}
}
